﻿using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.Serialization;

namespace RockBus.DataContracts
{
    public interface IEvaluationElementInfoBase
    {
        string Id { get; }
    }

    [DataContract(Namespace = StringConstants.RockBusNamespace)]
    //[KnownType(typeof(SubscriptionInfo))]
    //[KnownType(typeof(ReceivePortInfo))]
    public abstract class EvaluationElementInfoBase<TDetail> : IEvaluationElementInfoBase
        where TDetail : Detail
    {
        protected EvaluationElementInfoBase()
        {
        }

        protected EvaluationElementInfoBase(TDetail detail, IEnumerable<string> filterIds = null)
        {
            this.Detail = detail;
            this.FilterIds = new List<string>();
            if (null != filterIds)
            {
                ((List<string>)this.FilterIds).AddRange(filterIds);
            }
        }

        [DataMember]
        public TDetail Detail { get; set; }

        [IgnoreDataMember]
        public string Id { get { return this.Detail.Id; } }

        [DataMember]
        public IEnumerable<string> FilterIds { get; private set; }

        public void AddFilterIds(IEnumerable<FilterInfo> filterInfos)
        {
            ((List<string>)this.FilterIds).AddRange(filterInfos.Select((fi) => fi.Id));
        }

        public void AddFilterIds(params FilterInfo[] filterInfos)
        {
            this.AddFilterIds((IEnumerable<FilterInfo>)filterInfos);
        }

        public void AddFilterIds(IEnumerable<string> filterIds)
        {
            ((List<string>)this.FilterIds).AddRange(filterIds);
        }

        public void AddFilterIds(params string[] filterIds)
        {
            this.AddFilterIds((IEnumerable<string>)filterIds);
        }
    }

    [DataContract(Namespace = StringConstants.RockBusNamespace)]
    //[KnownType(typeof(SubscriptionDetail))]
    //[KnownType(typeof(ReceivePortDetail))]
    public abstract class Detail
    {
        protected Detail()
        {
        }

        protected Detail(string parentId, string id, string name, string endpointId, bool persistent, bool paused = false, DateTime? expiryDateTimeUtc = null, string requestPipelineId = null, string responsePipelineId = null)
        {
            this.ParentId = parentId;
            this.Id = id;
            this.Name = name;
            this.EndpointId = endpointId;
            this.Persistent = persistent;
            this.Paused = paused;
            this.ExpiryDateTimeUtc = expiryDateTimeUtc;
            this.RequestPipelineId = requestPipelineId;
            this.ResponsePipelineId = responsePipelineId;
        }

        [DataMember]
        public string ParentId { get; set; }

        [DataMember]
        public string Id { get; set; }

        [DataMember]
        public string Name { get; set; }

        [DataMember]
        public string EndpointId { get; set; }

        [DataMember]
        public bool Persistent { get; set; }

        [DataMember]
        public bool Paused { get; set; }

        [DataMember]
        public Nullable<DateTime> ExpiryDateTimeUtc { get; set; }

        [DataMember]
        public string RequestPipelineId { get; set; }

        [DataMember]
        public string ResponsePipelineId { get; set; }
    }

    [DataContract(Namespace = StringConstants.RockBusNamespace)]
    //[KnownType(typeof(PublisherInfo))]
    //[KnownType(typeof(SubscriberInfo))]
    public abstract class EvaluationElement
    {
        protected EvaluationElement()
        {
            this.Init();
        }

        protected EvaluationElement(string id, bool isStatic, IEnumerable<NamespaceInfo> namespaceInfos = null, IEnumerable<FilterInfo> filterInfos = null, IEnumerable<EndpointInfo> endpointInfos = null, IEnumerable<PipelineInfo> pipelineInfos = null, DateTime? modificationDateTimeUtc = null)
            : this()
        {
            this.Id = id;
            this.IsStatic = isStatic;
            if (modificationDateTimeUtc.HasValue)
            {
                this.ModificationDateTimeUtc = modificationDateTimeUtc.Value;
            }

            if (null != namespaceInfos)
            {
                foreach (var ni in namespaceInfos)
                {
                    this.NamespaceInfoDictionary.Add(ni.Prefix, ni);
                }
            }

            if (null != filterInfos)
            {
                foreach (var fi in filterInfos)
                {
                    this.FilterInfoDictionary.Add(fi.Id, fi);
                }
            }

            if (null != endpointInfos)
            {
                foreach (var ei in endpointInfos)
                {
                    this.EndpointInfoDictionary.Add(ei.Id, ei);
                }
            }

            if (null != pipelineInfos)
            {
                foreach (var pi in pipelineInfos)
                {
                    this.PipelineInfoDictionary.Add(pi.Id, pi);
                }
            }
        }

        [OnDeserialized]
        private void OnDeserialized(StreamingContext context)
        {
            this.Init();
        }

        private void Init()
        {
            if (null == this.NamespaceInfoDictionary)
            {
                this.NamespaceInfoDictionary = new CaseInsensitiveStringDictionary<NamespaceInfo>();
            }
            if (null == this.FilterInfoDictionary)
            {
                this.FilterInfoDictionary = new CaseInsensitiveStringDictionary<FilterInfo>();
            }
            if (null == this.EndpointInfoDictionary)
            {
                this.EndpointInfoDictionary = new CaseInsensitiveStringDictionary<EndpointInfo>();
            }
            if (null == this.PipelineInfoDictionary)
            {
                this.PipelineInfoDictionary = new CaseInsensitiveStringDictionary<PipelineInfo>();
            }
        }

        public void AddEndpointInfos(IEnumerable<EndpointInfo> endpointInfos)
        {
            foreach (var ei in endpointInfos)
            {
                if (!this.EndpointInfoDictionary.ContainsKey(ei.Id))
                {
                    this.EndpointInfoDictionary.Add(ei.Id, ei);
                }
            }
        }

        public void AddEndpointInfos(params EndpointInfo[] endpointInfos)
        {
            this.AddEndpointInfos((IEnumerable<EndpointInfo>)endpointInfos);
        }

        public void AddFilterInfos(IEnumerable<FilterInfo> filterInfos)
        {
            foreach (var fi in filterInfos)
            {
                if (!this.FilterInfoDictionary.ContainsKey(fi.Id))
                {
                    this.FilterInfoDictionary.Add(fi.Id, fi);
                }
            }
        }

        public void AddFilterInfos(params FilterInfo[] filterInfos)
        {
            this.AddFilterInfos((IEnumerable<FilterInfo>)filterInfos);
        }

        public void AddPipelineInfos(IEnumerable<PipelineInfo> pipelineInfos)
        {
            foreach (var pi in pipelineInfos)
            {
                if (!this.PipelineInfoDictionary.ContainsKey(pi.Id))
                {
                    this.PipelineInfoDictionary.Add(pi.Id, pi);
                }
            }
        }

        public void AddPipelineInfos(params PipelineInfo[] pipelineInfos)
        {
            this.AddPipelineInfos((IEnumerable<PipelineInfo>)pipelineInfos);
        }

        /// <summary>
        /// The Id of the EvaluationElement.
        /// </summary>
        [DataMember]
        public string Id { get; set; }

        /// <summary>
        /// Indicates if it is a statically configured EveluationElement (through static configuration in App/Web.config),
        ///  or if it's dynamically configured through the ConfigurationService.
        ///  Changes to static EvaluationElements are ignored by the ConfigurationService (i.e. when updating/deleting).
        ///  This property is used for read-only purposes, because when inserting/updating/deleting, any statically configured EveluationElements
        ///   will be ignored based on it's Id.
        /// </summary>
        [DataMember]
        public bool IsStatic { get; private set; }

        /// <summary>
        /// Holds the DateTime at which the Publisher was last modified.
        ///  In case of Cache Repository, this is the RockBus Server Utc time.
        ///  In case of the Db Repository, this is the Database Server Utc time.
        /// </summary>
        [DataMember]
        public DateTime ModificationDateTimeUtc { get; set; }

        /// <summary>
        /// Contains the NamespaceInfo objects.
        ///  Key: Prefix. Value: NamespaceInfo.
        /// </summary>
        [DataMember]
        public CaseInsensitiveStringDictionary<NamespaceInfo> NamespaceInfoDictionary { get; set; }

        /// <summary>
        /// Contains the FilterInfo objects.
        ///  Key: FilterId. Value: FilterInfo.
        /// </summary>
        [DataMember]
        public CaseInsensitiveStringDictionary<FilterInfo> FilterInfoDictionary { get; set; }

        /// <summary>
        /// Contains the EndpointInfo classes.
        ///  Key: EndpointId. Value: EndpointInfo.
        /// </summary>
        [DataMember]
        public CaseInsensitiveStringDictionary<EndpointInfo> EndpointInfoDictionary { get; set; }

        /// <summary>
        /// Contains the PipelineInfo objects.
        ///  Key: PipelineId. Value: PipelineInfo.
        /// </summary>
        [DataMember]
        public CaseInsensitiveStringDictionary<PipelineInfo> PipelineInfoDictionary { get; set; }
    }

    [DataContract(Namespace = StringConstants.RockBusNamespace)]
    //[KnownType(typeof(PublisherInfo))]
    //[KnownType(typeof(SubscriberInfo))]
    public abstract class EvaluationElement<TInfo, TDetail> : EvaluationElement
        where TInfo : EvaluationElementInfoBase<TDetail>
        where TDetail : Detail
    {
        protected EvaluationElement()
            : base()
        {
            this.Init();
        }

        protected EvaluationElement(string id, bool isStatic, IEnumerable<NamespaceInfo> namespaceInfos = null, IEnumerable<FilterInfo> filterInfos = null, IEnumerable<EndpointInfo> endpointInfos = null, IEnumerable<PipelineInfo> pipelineInfos = null, IEnumerable<TInfo> infos = null, DateTime? modificationDateTimeUtc = null)
            : base(id, isStatic, namespaceInfos, filterInfos, endpointInfos, pipelineInfos, modificationDateTimeUtc)
        {
            this.Init();

            if (null != infos)
            {
                foreach (var info in infos)
                {
                    this.InfoDictionary.Add(info.Id, info);
                }
            }
        }

        protected EvaluationElement(IEnumerable<TInfo> infos)
            : this()
        {
            if (null != infos)
            {
                foreach (var info in infos)
                {
                    this.InfoDictionary.Add(info.Id, info);
                }
            }
        }

        [OnDeserialized]
        private void OnDeserialized(StreamingContext context)
        {
            this.Init();
        }

        private void Init()
        {
            if (null == this.InfoDictionary)
            {
                this.InfoDictionary = new CaseInsensitiveStringDictionary<TInfo>();
            }
        }

        /// <summary>
        /// Contains the FilterInfo to SubscriptionInfo mapping.
        ///  Key: SubscriptionId. Value: SubscriptionInfo.
        /// </summary>
        [DataMember]
        public CaseInsensitiveStringDictionary<TInfo> InfoDictionary { get; set; }

        public abstract void PurgeExpired(DateTime utcNow);
    }

    [DataContract(Namespace = StringConstants.RockBusNamespace)]
    //[KnownType(typeof(SubscriberConfiguration))]
    //[KnownType(typeof(PublisherConfiguration))]
    public abstract class EvaluatorConfigurationBase<TEvaluationElement, TInfo, TDetail> : ICloneable
        where TEvaluationElement : EvaluationElement<TInfo, TDetail>
        where TInfo : EvaluationElementInfoBase<TDetail>
        where TDetail : Detail
    {
        protected EvaluatorConfigurationBase()
        {
            this.Init();
        }

        protected EvaluatorConfigurationBase(IEnumerable<TEvaluationElement> elements)
            : this()
        {
            if (null != elements)
            {
                foreach (var element in elements)
                {
                    this.EvaluationElementDictionary.Add(element.Id, element);
                }
            }
        }

        [OnDeserialized]
        private void OnDeserialized(StreamingContext context)
        {
            this.Init();
        }

        private void Init()
        {
            if (null == this.EvaluationElementDictionary)
            {
                this.EvaluationElementDictionary = new CaseInsensitiveStringDictionary<TEvaluationElement>();
            }
        }

        [DataMember]
        public CaseInsensitiveStringDictionary<TEvaluationElement> EvaluationElementDictionary { get; set; }

        private static object _lockObject = new object();
        private static DataContractSerializer _serializer;

        private DataContractSerializer Serializer
        {
            get
            {
                if (null == _serializer)
                {
                    lock (_lockObject)
                    {
                        if (null == _serializer)
                        {
                            _serializer = new DataContractSerializer(this.GetType());
                        }
                    }
                }
                return _serializer;
            }
        }

        public object Clone()
        {
            using (MemoryStream ms = new MemoryStream())
            {
                this.Serializer.WriteObject(ms, this);
                ms.Position = 0;
                object obj = Serializer.ReadObject(ms);
                return obj;
            }
        }

        /// <summary>
        /// Merge-overwrites this EvaluationElements with the other EvaluationElements.
        /// </summary>
        /// <param name="other"></param>
        public void Merge(EvaluatorConfigurationBase<TEvaluationElement, TInfo, TDetail> other)
        {
            this.EvaluationElementDictionary.Merge(other.EvaluationElementDictionary);
        }

        public void SetModificationDateTime(DateTime utcNow)
        {
            foreach (KeyValuePair<string, TEvaluationElement> kvp in this.EvaluationElementDictionary)
            {
                kvp.Value.ModificationDateTimeUtc = utcNow;
            }
        }

        public void PurgeExpired(DateTime utcNow)
        {
            foreach (KeyValuePair<string, TEvaluationElement> kvp in this.EvaluationElementDictionary)
            {
                kvp.Value.PurgeExpired(utcNow);
            }
        }

        public Dictionary<Tuple<string, string>, PipelineInfo> CreatePipelineInfoDictionary()
        {
            var dict = new Dictionary<Tuple<string, string>, PipelineInfo>();
            foreach (var elem in this.EvaluationElementDictionary.Values)
            {
                foreach (var info in elem.InfoDictionary.Values)
                {
                    if (!string.IsNullOrEmpty(info.Detail.RequestPipelineId))
                    {
                        var key = new Tuple<string, string>(elem.Id, info.Detail.RequestPipelineId);
                        var pi = elem.PipelineInfoDictionary[info.Detail.RequestPipelineId];
                        dict[key] = pi;
                    }
                    if (!string.IsNullOrEmpty(info.Detail.ResponsePipelineId))
                    {
                        var key = new Tuple<string, string>(elem.Id, info.Detail.ResponsePipelineId);
                        var pi = elem.PipelineInfoDictionary[info.Detail.ResponsePipelineId];
                        dict[key] = pi;
                    }
                }
            }
            return dict;
        }
    }
}